#include "ipaddr.h"
#include <fmt/core.h>
#include <sstream>

namespace wilson::net
{
    ipv4_address::ipv4_address(const ipv4_address& other) noexcept
        : ia_data{ other.ia_data }
    {
    }

    ipv6_address::ipv6_address(::std::initializer_list<uint16_t> vals)
    {
        if (vals.size() != 8) [[unlikely]]
        {
            throw ::std::logic_error{ 
                "You need pass a array which type is uint16_t "
                "and it's size is 8"
            };
        }

        for (size_t i = 0; i < 8; ++i)
        {
            i6a_data[i] = ::std::data(vals)[i];
        }
    }

    ipv6_address::ipv6_address(::std::span<uint16_t> vals)
    {
        if (vals.size() != 8) [[unlikely]]
        {
            throw ::std::logic_error{ 
                "You need pass a array which type is uint16_t "
                "and it's size is 8"
            };
        }
        memcpy(i6a_data.data(), vals.data(), vals.size_bytes());
    }

    ipv6_address::ipv6_address(::std::span<uint32_t> vals)
    {
        if (vals.size() != 4) [[unlikely]]
        {
            throw ::std::logic_error{ 
                "You need pass a array which type is uint32_t "
                "and it's size is 4"
            };
        }
        memcpy(i6a_data.data(), vals.data(), vals.size_bytes());
    }

    ipv6_address::ipv6_address(::std::span<uint64_t> vals)
    {
        if (vals.size() != 2) [[unlikely]]
        {
            throw ::std::logic_error{ 
                "You need pass a array which type is uint64_t "
                "and it's size is 2"
            };
        }
        memcpy(i6a_data.data(), vals.data(), vals.size_bytes());
    }

    ::std::string ipv6_address::to_string() const
    {
        ::std::stringstream ss;
        ss << "[";
        ::std::span<const uint16_t> addr = as_uint16s();
        bool used_zero{};
        for (size_t i = 0; i < addr.size(); ++i)
        {
            if (addr[i] == 0 && !used_zero) continue;
            if (i && addr[i - 1] == 0 && !used_zero)
            {
                ss << ":";
                used_zero = true;
            }
            if (i) ss << ":";
            ss << ::std::hex << static_cast<int>(addr[i]);
        }
        ss << "]";
        return ss.str();
    }

    ipv6_address to_v6addr(ipv4_address v4a)
    {
        // embedded ipv6 address
        // well-known prefix 64:FF9B with ipv4 32-bits address

        // reference: https://www.rfc-editor.org/rfc/rfc6052

        // 0               16              32 
        // 12345678123456781234567812345678
        // |       |       |       |       |
        // 00000000011001001111111110011011     => 64:FF9B

        // 00000000000000000000000000000000
        // 00000000000000000000000000000000     => ::

        uint16_t prefix[8]{ 0x64, 0xFF9B };
        *reinterpret_cast<uint32_t*>(&prefix[6]) = v4a.to_uint32();
        ipv6_address ret{};
        memcpy(ret.i6a_data.data(), prefix, sizeof(prefix));
        return ret;
    }

    ::std::string ipv4_address::to_string() const
    {
        return fmt::format("{}.{}.{}.{}", 
                ia_data[0], ia_data[1], ia_data[2], ia_data[3]);
    }

    uint32_t ipv4_address::to_uint32() const noexcept
    {
        return *reinterpret_cast<const uint32_t*>(ia_data.data());
    }

    ::std::span<const uint8_t> ipv4_address::as_uint8s() const noexcept
    {
        return { ia_data.data(), ia_data.size() };
    }

    const uint8_t* ipv6_address::data() const noexcept
    {
        return reinterpret_cast<const uint8_t*>(i6a_data.data());
    }

    ::std::span<const uint64_t> ipv6_address::as_uint64s() const noexcept
    {
        return { reinterpret_cast<const uint64_t*>(data()), 2 };
    }

    ::std::span<const uint16_t> ipv6_address::as_uint16s() const noexcept
    {
        return { reinterpret_cast<const uint16_t*>(data()), 8 };
    }

    ipv4_address& ipv4_address::operator+=(int i)
    {
        *reinterpret_cast<uint32_t*>(ia_data.data()) += i;
        return *this;
    }

    ipv4_address& ipv4_address::operator-=(int i)
    {
        *reinterpret_cast<uint32_t*>(ia_data.data()) -= i;
        return *this;
    }

    ipv4_address& ipv4_address::operator++()
    {
        return (*this) += 1;
    }

    ipv4_address& ipv4_address::operator--()
    {
        return (*this) -= 1;
    }

    ipv4_address  ipv4_address::operator++(int)
    {
        ipv4_address ret{ *this };
        this->operator++();
        return ret;
    }

    ipv4_address  ipv4_address::operator--(int)
    {
        ipv4_address ret{ *this };
        this->operator--();
        return ret;
    }

    ::std::strong_ordering 
    operator<=>(ipv4_address a, ipv4_address b)
    {
        return a.to_uint32() <=> b.to_uint32();
    }

    ::std::strong_ordering 
    operator<=>(const ipv6_address& a, const ipv6_address& b)
    {
        auto a_span = a.as_uint64s();
        auto b_span = b.as_uint64s();
        auto ret = a_span[0] <=> b_span[0];
        if (ret == ::std::strong_ordering::equal)
            return a_span[1] <=> b_span[1];
        return ret;
    }
}
